/*
 * pa1-2.c
 *
 * This file provides the "shell" for the first programming assignment for PA1.
 * It reads in one JPEG files at the start, leaving them in buffers that
 * the students can manipulate then outputs a file so that results may be
 * compared with input. Currently, the earlier input(s) are input files, and
 * the later ones are output files.
 */

/******************** CONTROLLABLE PARAMETERS *******************/

/* Define NO_FIRST if you want to discard the performance of the first run
 * (most likely).
 */
/* #define NO_FIRST */
//#define P2_BASE

/* The number of files can be adjusted with the following parameters: */

/* For PA1-2, this is 1 */
#define NUM_INPUTS		1
#define MAX_INPUTS		1

/* NUM_OUTPUTS is always fixed */
#define NUM_OUTPUTS		1

/* A quality level of 2-100 can be provided (default = 75, high quality = ~95,
 * low quality = ~25, utter pixellation = 2).  This controls the degree of
 * compression at the output.
 */
#define OUT_QUALITY		75

/************************* END OF PARAMETERS *******************/

#define RUNS_COMMAND    1
#define RUNS_PROCS      1

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <sys/time.h>
#include <math.h>
#include "jpeglib.h"
#include <pthread.h>

/*
 * IMAGE DATA FORMATS:
 *
 * The standard input image format is a rectangular array of pixels, with
 * each pixel having the same number of "component" values (color channels).
 * Each pixel row is an array of JSAMPLEs (which typically are unsigned chars).
 * If you are working with color data, then the color values for each pixel
 * must be adjacent in the row; for example, R,G,B,R,G,B,R,G,B,... for 24-bit
 * RGB color.
 */

/* The "frame structure" structure contains an image frame (in RGB or grayscale
 * formats) for passing around the CS315a projects.
 */
typedef struct frame_struct {
    JSAMPLE *image_buffer;	/* Points to large array of
                                 * R,G,B-order/grayscale data.
                                 *
                                 * Access directly with:
                                 *   image_buffer[num_components*pixel +
                                 *     component]
                                 */
    JSAMPLE **row_pointers;	/* Points to an array of pointers to the
                                 * beginning of each row in the image buffer.
                                 * Use to access the image buffer in a row-wise
                                 * fashion, with:
                                 *   row_pointers[row][num_components*pixel +
                                 *     component]
                                 */
    int image_height;		/* Number of rows in image */
    int image_width;		/* Number of columns in image */
    int num_components;         /* Number of components (usually RGB=3 or
                                 * gray=1)
                                 */
} frame_struct_t;
typedef frame_struct_t *frame_ptr;

typedef struct arg_struct {
	int i;
	int n;
	int tc;
	frame_ptr from;
} arg_t;

int rHist[256], gHist[256], bHist[256];			/* RGB histogram buckets */
int sHist[766];									/* R+G+B histogram buckets */

pthread_mutex_t lock_r[8], lock_g[8], lock_b[8];
pthread_mutex_t lock_s[24];

/* These arrays of frame structure pointers point to all of the frames that
 * are read at the beginning of the program or written at the end.  Note
 * that you can simply move input frame pointers to output frame pointers if
 * you do "in-place" transformations (leaving output data right in the input
 * buffers).
 */
frame_ptr input_frames[MAX_INPUTS];	/* Pointers to input frames */
frame_ptr output_frames[NUM_OUTPUTS];	/* Pointers to output frames */
int num_procs;		/* Number of processors, for parallel use */

/* Function prototypes */

/* Your code */
void CS315a_function();

/* Read/write JPEGs, for program startup & shutdown */
/* YOU SHOULD NOT NEED TO USE THESE AT ALL */
void write_JPEG_file (char * filename, frame_ptr p_info, int quality);
frame_ptr read_JPEG_file (char * filename);

/* Allocate/deallocate frame buffers, USE AS NECESSARY! */
frame_ptr allocate_frame(int height, int width, int num_components);
void destroy_frame(frame_ptr kill_me);


/******************** START OF ASSIGNMENT SECTION *******************/

void* hist_count(void* arg_v) {
	arg_t* arg = (arg_t*) arg_v;
	int i = arg->i;
	int n = arg->n;
	int tc = arg->tc;
	frame_ptr from = arg->from;
	int j;
	for (; i < n; i += tc) {
        for (j = 0; j < from->image_width; j++) {
			int pixel_red = from->row_pointers[i][(from->num_components)*j+0];			
			pthread_mutex_lock( &lock_r[pixel_red/32] );
			rHist[pixel_red]++;
			pthread_mutex_unlock( &lock_r[pixel_red/32] );

			int pixel_green = from->row_pointers[i][(from->num_components)*j+1];
			pthread_mutex_lock( &lock_g[pixel_green/32] );
			gHist[pixel_green]++;
			pthread_mutex_unlock( &lock_g[pixel_green/32] );

			int pixel_blue = from->row_pointers[i][(from->num_components)*j+2];
			pthread_mutex_lock( &lock_b[pixel_blue/32] );
			bHist[pixel_blue]++;    
			pthread_mutex_unlock( &lock_b[pixel_blue/32] );

			int pixel_sum = pixel_red + pixel_green + pixel_blue;			
			pthread_mutex_lock( &lock_s[pixel_sum/32] );
			sHist[pixel_sum]++;
			pthread_mutex_unlock( &lock_s[pixel_sum/32] );
		}
	}
}



/* While you're free to tweak other parts of this file, most of your
 * new code should be located here (or in another file, if it's large).
 */

#define MAX(a,b) (a>b?a:b)
#define MIN(a,b) (a<b?a:b)


void CS315a_function() {
    int i, j, k;

    frame_ptr from, to;

    from = input_frames[0];

    to = output_frames[0] =
        allocate_frame(from->image_height,
                       from->image_width,
                       from->num_components);
	/* Init buckets */
	for (i = 0; i < 256; i++) {
		rHist[i] = 0;
		gHist[i] = 0;
		bHist[i] = 0;
	}
	for (i = 0; i < 766; i++) 
		sHist[i] = 0;
	
	/* Init locks */
	for (i = 0; i < 8; i++) {
		pthread_mutex_init(&lock_r[i], NULL);
		pthread_mutex_init(&lock_g[i], NULL);
		pthread_mutex_init(&lock_b[i], NULL);
	}
	for (i = 0; i < 24; i++) {
		pthread_mutex_init(&lock_s[i], NULL);
	}
                       
	int thread_count = num_procs;                   
	pthread_t *th = malloc(sizeof(pthread_t) * thread_count);
	arg_t **arg = malloc(sizeof(arg_t *) * thread_count);
	for (i = 0; i < thread_count; i++)
		arg[i] = malloc(sizeof(arg_t));	
	
	for (i = 1; i < thread_count; i++) {
		arg[i]->i = i; arg[i]->n = from->image_height; arg[i]->tc = thread_count;
		arg[i]->from = from;
		pthread_create(&th[i], NULL, &hist_count, (void *) arg[i]);
	}
	arg[0]->i = 0; arg[0]->n = from->image_height; arg[0]->tc = thread_count; 
	arg[0]->from = from;
	hist_count(arg[0]);
	
	for (i = 1; i < thread_count; i++)
		pthread_join(th[i], NULL);

	for (i = 0; i < 8; i++) {
		pthread_mutex_destroy(&lock_r[i]);
		pthread_mutex_destroy(&lock_g[i]);
		pthread_mutex_destroy(&lock_b[i]);
	}
	for (i = 0; i < 24; i++) {
		pthread_mutex_destroy(&lock_s[i]);
	}		
		
}

/******************** END OF ASSIGNMENT SECTION *******************/

/*
 * write_JPEG_file writes out the contents of an image buffer to a JPEG.
 * A quality level of 2-100 can be provided (default = 75, high quality = ~95,
 * low quality = ~25, utter pixellation = 2).  Note that unlike read_JPEG_file,
 * it does not do any memory allocation on the buffer passed to it.
 */

void write_JPEG_file (char * filename, frame_ptr p_info, int quality) {
    struct jpeg_compress_struct cinfo;
    struct jpeg_error_mgr jerr;
    FILE * outfile;		/* target file */

    /* Step 1: allocate and initialize JPEG compression object */
    cinfo.err = jpeg_std_error(&jerr);
    jpeg_create_compress(&cinfo);

    /* Step 2: specify data destination (eg, a file) */
    /* Note: steps 2 and 3 can be done in either order. */

    if ((outfile = fopen(filename, "wb")) == NULL) {
        fprintf(stderr, "ERROR: Can't open output file %s\n", filename);
        exit(1);
    }
    jpeg_stdio_dest(&cinfo, outfile);

    /* Step 3: set parameters for compression */

    /* Set basic picture parameters (not optional) */
    cinfo.image_width = p_info->image_width; 	/* image width and height, in pixels */
    cinfo.image_height = p_info->image_height;
    cinfo.input_components = p_info->num_components; /* # of color components per pixel */
    if (p_info->num_components == 3)
        cinfo.in_color_space = JCS_RGB; 	/* colorspace of input image */
    else if (p_info->num_components == 1)
        cinfo.in_color_space = JCS_GRAYSCALE;
    else {
        fprintf(stderr, "ERROR: Non-standard colorspace for compressing!\n");
        exit(1);
    } 
    /* Fill in the defaults for everything else, then override quality */
    jpeg_set_defaults(&cinfo);
    jpeg_set_quality(&cinfo, quality, TRUE /* limit to baseline-JPEG values */);

    /* Step 4: Start compressor */
    jpeg_start_compress(&cinfo, TRUE);

    /* Step 5: while (scan lines remain to be written) */
    /*           jpeg_write_scanlines(...); */
    while (cinfo.next_scanline < cinfo.image_height) {
        (void) jpeg_write_scanlines(&cinfo, &(p_info->row_pointers[cinfo.next_scanline]), 1);
    }

    /* Step 6: Finish compression & close output */

    jpeg_finish_compress(&cinfo);
    fclose(outfile);

    /* Step 7: release JPEG compression object */
    jpeg_destroy_compress(&cinfo);
}


/*
 * read_JPEG_file reads the contents of a JPEG into an image buffer, which
 * is automatically allocated after the size of the image is determined.
 * We want to return a frame struct on success, NULL on error.
 */

frame_ptr read_JPEG_file (char * filename) {
    /* This struct contains the JPEG decompression parameters and pointers to
     * working space (which is allocated as needed by the JPEG library).
     */
    struct jpeg_decompress_struct cinfo;
    struct jpeg_error_mgr jerr;
    FILE * infile;		/* source file */
    frame_ptr p_info;		/* Output frame information */

    JSAMPLE *realBuffer;
    JSAMPLE **buffer;		/* Output row buffer */
    int row_stride;		/* physical row width in output buffer */

    /* Step 1: allocate and initialize JPEG decompression object */
    cinfo.err = jpeg_std_error(&jerr);
    jpeg_create_decompress(&cinfo);

    /* Step 2: open & specify data source (eg, a file) */
    if ((infile = fopen(filename, "rb")) == NULL) {
        fprintf(stderr, "ERROR: Can't open input file %s\n", filename);
        exit(1);
    }
    jpeg_stdio_src(&cinfo, infile);

    /* Step 3: read file parameters with jpeg_read_header() */
    (void) jpeg_read_header(&cinfo, TRUE);

    /* Step 4: use default parameters for decompression */

    /* Step 5: Start decompressor */
    (void) jpeg_start_decompress(&cinfo);

    /* Step X: Create a frame struct & buffers and fill in the blanks */
    fprintf(stderr, "  Opened %s: height = %d, width = %d, c = %d\n",
            filename, cinfo.output_height, cinfo.output_width, cinfo.output_components);
    p_info = allocate_frame(cinfo.output_height, cinfo.output_width, cinfo.output_components);

    /* Step 6: while (scan lines remain to be read) */
    /*           jpeg_read_scanlines(...); */
    while (cinfo.output_scanline < cinfo.output_height) {
        (void) jpeg_read_scanlines(&cinfo, &(p_info->row_pointers[cinfo.output_scanline]), 1);
    }

    /* Step 7: Finish decompression */
    (void) jpeg_finish_decompress(&cinfo);

    /* Step 8: Release JPEG decompression object & file */
    jpeg_destroy_decompress(&cinfo);
    fclose(infile);

    /* At this point you may want to check to see whether any corrupt-data
     * warnings occurred (test whether jerr.pub.num_warnings is nonzero).
     */

    /* And we're done! */
    return p_info;
}

/*
 * allocate/destroy_frame allocate a frame_struct_t and fill in the
 *  blanks appropriately (including allocating the actual frames), and
 *  then destroy them afterwards.
 */

frame_ptr allocate_frame(int height, int width, int num_components) {
    int row_stride;		/* physical row width in output buffer */
    int i;
    frame_ptr p_info;		/* Output frame information */

    /* JSAMPLEs per row in output buffer */
    row_stride = width * num_components;

    /* Basic struct and information */
    if ((p_info = malloc(sizeof(frame_struct_t))) == NULL) {
        fprintf(stderr, "ERROR: Memory allocation failure\n");
        exit(1);
    }
    p_info->image_height = height;
    p_info->image_width = width;
    p_info->num_components = num_components;

    /* Image array and pointers to rows */
    if ((p_info->row_pointers = malloc(sizeof(JSAMPLE *) * height)) == NULL) {
        fprintf(stderr, "ERROR: Memory allocation failure\n");
        exit(1);
    }
    if ((p_info->image_buffer = malloc(sizeof(JSAMPLE) * row_stride * height)) == NULL) {
        fprintf(stderr, "ERROR: Memory allocation failure\n");
        exit(1);
    }
    for (i=0; i < height; i++)
        p_info->row_pointers[i] = & (p_info->image_buffer[i * row_stride]);

    /* And send it back! */
    return p_info;
}

void destroy_frame(frame_ptr kill_me) {
    free(kill_me->image_buffer);
    free(kill_me->row_pointers);
    free(kill_me);
}

/* The shell's main routine.  Consists of several steps:
 *  1> Reads JPEGs using first command-line parameter(s)
 *  2> Records the process's elapsed time
 *  3> Lets the user play with the images
 *  4> Checks the elapsed time again, prints out the difference
 *  5> Writes JPEGs out using last command-line parameter(s)
 */
int main(int argc, char *argv[]) {
    int i;
    int num_inputs; /* Number of JPEG input-output files */
    int num_runs, run; /* Number of runs, for multi-run code */
    double run_time, total_time; /* Amount of time we've run */
    struct timeval start_time, end_time; /* Time before/after user code */

    FILE* outputFile;

    /* Step 0A: Check & process command line */
    if (argc != 1 + NUM_INPUTS + NUM_OUTPUTS + RUNS_COMMAND + RUNS_PROCS) {
        fprintf(stderr, "Usage: %s runs procs input1.jpg %shistogramOutput\n",
                argv[0],
                NUM_INPUTS > 1 ? "input2.jpg " : "");
        exit(1);
    }

    num_inputs = argc - 1 - NUM_OUTPUTS - RUNS_COMMAND - RUNS_PROCS;
    outputFile = fopen(argv[argc - NUM_OUTPUTS], "w");

    num_runs = atoi(argv[1]);
    if (num_runs < 1) num_runs = 1;
    if (num_runs > 10) num_runs = 10; /* Change if you like LOTS of runs */
    fprintf(stderr, "Making %d runs . . .\n", num_runs);
    num_procs = atoi(argv[1 + RUNS_COMMAND]);
    if (num_procs < 1) num_procs = 1;
    if (num_procs > 256) num_procs = 256;
    fprintf(stderr, "Using %d processors . . .\n", num_procs);

    /* Step 1: Get some JPEGs into memory, uncompressed */
    for (i=0; i < num_inputs; i++)
        input_frames[i] = read_JPEG_file(argv[i+1+RUNS_COMMAND+RUNS_PROCS]);

    /* Loop over multiple runs, if desired */

    total_time = 0.0;
    run = 0;
    while ((run < num_runs)) {
        /* Step 2: Record elapsed time */
        gettimeofday(&start_time, NULL);

        /* Step 3: Call a user function! */
        CS315a_function();

        /* Step 4: Check & print elapsed time */
        gettimeofday(&end_time, NULL);
        run_time = ((double)(end_time.tv_sec) + (double)(end_time.tv_usec)/1000000.0)
            - ((double)(start_time.tv_sec) + (double)(start_time.tv_usec)/1000000.0);

        fprintf(stderr, "%d. ELAPSED  TIME = %20.3f sec\n", run, run_time);
        total_time += run_time;
#ifdef NO_FIRST
        if (run == 0) {
            fprintf(stderr, "  . . . first run discarded\n");
            total_time = 0.0;
        }
#endif
        run++;
    }

    /* Print out final, average time, if desired */
#ifdef NO_FIRST
    fprintf(stderr, "AVERAGE ELAPSED TIME = %20.3f seconds\n",
            total_time / ((double)(run - 1)));
#else
    fprintf(stderr, "AVERAGE ELAPSED TIME = %20.3f seconds\n",
            total_time / ((double)run));
#endif

    /* Step 5: Write out histogram: uncomment when you have globals defined */
    
    for (i=0; i < 256; i++) {
        fprintf(outputFile, "%3u: R:%8u G:%8u B:%8u S0:%8u S1:%8u S2:%8u\n",
                i, rHist[i], gHist[i], bHist[i], sHist[i],
                sHist[i+256], (i+512 < 766 ? sHist[i+512] : 0));
    }
    
}
